//File Header
//////////////////////////////////////////////////////////////////////////////
//
//                     Copyright Yuriy Silvestrov 2007
//
////////////////////////////////////////////////////////////////////////////////
//
// This file "Xsd2CsTraversor"
// part of XSD Configuration Section Generator
// Started by "yuriy@silvestrov.com" on 3/1/2007
// Last edit by "$Author$" on $Date$
//
////////////////////////////////////////////////////////////////////////////////
//
// Change log ()
//
//
//
////////////////////////////////////////////////////////////////////////////////

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using Microsoft.CSharp;
using XsdConfigSection.Resources;

namespace XsdConfigSection
{
	///<summary>
	/// Traverse XML Schema file and builds .cs code for configuration section
	///</summary>
	public class Xsd2CsTraversor : XsdTraversor
	{
		#region attributes

		private static CodeGeneratorOptions codeGeneratorOptions = null;
		private string codeNamespaceName = "SampleNamespace";
		private bool isCreatePartialClasses = false;
		private string xmlNamespaceName = "config";
		private string configurationFileName = null;

		#endregion attributes

		#region constructors

		public Xsd2CsTraversor(XmlSchemaSet schemaSet)
			: base(schemaSet)
		{
		}

		public Xsd2CsTraversor(XmlSchema schema)
			: base(schema)
		{
		}

		#endregion constructors

		#region properites

		private static CodeGeneratorOptions CodeGeneratorOptions
		{
			get
			{
				if (codeGeneratorOptions == null)
				{
					codeGeneratorOptions = new CodeGeneratorOptions();
					codeGeneratorOptions.VerbatimOrder = true;
					codeGeneratorOptions.IndentString = "\t";
					codeGeneratorOptions.ElseOnClosing = false;
					codeGeneratorOptions.BlankLinesBetweenMembers = true;
				}
				return codeGeneratorOptions;
			}
		}

		public string CodeNamespaceName
		{
			get { return codeNamespaceName; }
			set { codeNamespaceName = value; }
		}

		public bool IsCreatePartialClasses
		{
			get { return isCreatePartialClasses; }
			set { isCreatePartialClasses = value; }
		}

		public string XmlNamespaceName
		{
			get { return xmlNamespaceName; }
			set { xmlNamespaceName = value; }
		}

		#endregion properites

		#region events and delegates

		#endregion events and delegates

		#region methods

		public void GenerateCode(TextWriter sw)
		{
			ICodeGenerator codeGenerator = new CSharpCodeProvider().CreateGenerator(sw);
			CodeNamespace cnsCodeDom = CreateAndFillNamespace();
			codeGenerator.GenerateCodeFromNamespace(cnsCodeDom, sw, CodeGeneratorOptions);
		}

		private CodeNamespace CreateAndFillNamespace()
		{
			CodeNamespace codeNamespace = new CodeNamespace(CodeNamespaceName);
			codeNamespace.Comments.Add(new CodeCommentStatement("This file is autogenerated by a tool."));
			codeNamespace.Comments.Add(
				new CodeCommentStatement("Please do not make any changes as they will be lost after next generation of a file."));
			codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
			codeNamespace.Imports.Add(new CodeNamespaceImport("System.Configuration"));
			//codeNamespace.Imports.Add(new CodeNamespaceImport("System.Windows.Forms"));

			foreach (XmlSchema schema in SchemaSet.Schemas())
			{
				foreach (XmlSchemaElement element in schema.Elements.Values)
				{
					CodeTypeDeclaration mainClass = new CodeTypeDeclaration();
					mainClass.Name = ConvertToClassName(element.Name) + "Section";
					mainClass.IsClass = true;
					mainClass.TypeAttributes = TypeAttributes.Public;
					mainClass.IsPartial = IsCreatePartialClasses;
					CommentClassOrProperty(element, mainClass);

					//find out namespace and fill global property if found
					if (element.UnhandledAttributes != null)
					{
						foreach (XmlAttribute unhandledAttribute in element.UnhandledAttributes)
						{
							if (unhandledAttribute.LocalName == "namespace")
							{
								XmlNamespaceName = unhandledAttribute.Value;
							}
							else if (unhandledAttribute.LocalName == "configuration-file-name")
							{
								ConfigurationFileName = unhandledAttribute.Value;
							}
						}
					}

					CodeTypeReference baseRef = new CodeTypeReference("ConfigurationSection");
					mainClass.BaseTypes.Add(baseRef);

					AddDummyNSProperty(mainClass);

					MakeSingleton(mainClass, element.Name);

					if (element.RefName.IsEmpty)
					{
						XmlSchemaType type = element.ElementSchemaType;
						XmlSchemaComplexType complexType = type as XmlSchemaComplexType;
						if (complexType != null && complexType.Name == null)
						{
							XmlSchemaGroupBase baseParticle = complexType.ContentTypeParticle as XmlSchemaSequence;
							if (baseParticle != null)
							{
								foreach (XmlSchemaParticle subParticle in baseParticle.Items)
								{
									XmlSchemaElement propertyElement = subParticle as XmlSchemaElement;
									if (propertyElement != null)
									{
										CodeMemberProperty property = new CodeMemberProperty();
										property.Name = ConvertToClassName(propertyElement.Name);
										string propertyClassName = ConvertToClassName(propertyElement.Name) + "Element";
                                        if (propertyElement.ElementSchemaType is XmlSchemaSimpleType)
                                        {
                                            property.Type = new CodeTypeReference(propertyElement.ElementSchemaType.Datatype.ValueType.FullName);
                                        }
                                        else //propertyElement.SchemaType is XmlSchemaComplexType
                                        {
                                            property.Type = new CodeTypeReference(propertyClassName);
                                        }
										property.Attributes = MemberAttributes.Public;
										CommentClassOrProperty(propertyElement, property);
										property.GetStatements.Add(
											new CodeMethodReturnStatement(
                                                new CodeCastExpression(property.Type,
																	   new CodeIndexerExpression(new CodeBaseReferenceExpression(),
																								 new CodePrimitiveExpression(propertyElement.Name)))));

										CodeAttributeDeclaration cpAttribute =
											new CodeAttributeDeclaration("ConfigurationProperty",
																		 new CodeAttributeArgument(
																			new CodePrimitiveExpression(propertyElement.Name)));
										property.CustomAttributes.Add(cpAttribute);

										mainClass.Members.Add(property);

										ProcessSectionElementContent(mainClass, propertyElement);
									}
								}
								ProcessAttributes(mainClass, element);
							}
						}
					}
					codeNamespace.Types.Add(mainClass);
				}
			}

			return codeNamespace;
		}

		private string ConfigurationFileName
		{
			get { return configurationFileName; }
			set { configurationFileName = value; }
		}

		private void MakeSingleton(CodeTypeDeclaration singletonClass, string xmlSectionName)
		{
			CodeMemberField instance = new CodeMemberField(new CodeTypeReference(singletonClass.Name), "instance");
			instance.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			singletonClass.Members.Add(instance);

			CodeMemberField locker = new CodeMemberField(typeof(object), "locker");
			locker.InitExpression = new CodeObjectCreateExpression(typeof (object));
			locker.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			singletonClass.Members.Add(locker);

			CodeMemberField refreshNeeded = new CodeMemberField("System.Boolean", "isRefreshNeeded");
			refreshNeeded.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			singletonClass.Members.Add(refreshNeeded);

			string xmlFullNamespaceName = XmlNamespaceName + ":" +
										  xmlSectionName;

			CodeMemberMethod getSection = new CodeMemberMethod();
			getSection.Name = "GetConfigurationSection";
			getSection.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			getSection.ReturnType = new CodeTypeReference(singletonClass.Name);
			if (ConfigurationFileName != String.Empty && ConfigurationFileName != null)
			{
				/*
				ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
				map.ExeConfigFilename = String.Format(@"{0}\{1}", AppDomain.CurrentDomain.BaseDirectory, "main.config");
				return (SpeSettingsSection)ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None).GetSection("spe:spe-settings");
				*/

				getSection.Statements.Add(
					new CodeVariableDeclarationStatement("ExeConfigurationFileMap", "map",
					                                     new CodeObjectCreateExpression("ExeConfigurationFileMap")));
				getSection.Statements.Add(
					new CodeAssignStatement(
						new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("map"), "ExeConfigFilename"),
						new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("String"), "Format", new CodePrimitiveExpression(@"{0}\{1}"), new CodePropertyReferenceExpression(new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("AppDomain"), "CurrentDomain"), "BaseDirectory"), new CodePrimitiveExpression(ConfigurationFileName))));
				getSection.Statements.Add(new CodeMethodReturnStatement(
				                          	new CodeCastExpression(new CodeTypeReference(singletonClass.Name),
				                          	                       new CodeMethodInvokeExpression(
				                          	                       	new CodeMethodInvokeExpression(
				                          	                       		new CodeTypeReferenceExpression("ConfigurationManager"),
				                          	                       		"OpenMappedExeConfiguration",
				                          	                       		new CodeVariableReferenceExpression("map"),
				                          	                       		new CodePropertyReferenceExpression(
				                          	                       			new CodeTypeReferenceExpression("ConfigurationUserLevel"),
				                          	                       			"None")),
				                          	                       	"GetSection",
				                          	                       	new CodePrimitiveExpression(xmlFullNamespaceName)))));
			}
			else
			{
				getSection.Statements.Add(new CodeMethodReturnStatement(
				                          	new CodeCastExpression(new CodeTypeReference(singletonClass.Name),
				                          	                       new CodeMethodInvokeExpression(
				                          	                       	new CodeSnippetExpression("ConfigurationManager"),
				                          	                       	"GetSection",
				                          	                       	new CodePrimitiveExpression(xmlFullNamespaceName)))));
			}
			singletonClass.Members.Add(getSection);

			CodeMemberProperty instanceProperty = new CodeMemberProperty();
			instanceProperty.Name = "Instance";
			instanceProperty.Type = new CodeTypeReference(singletonClass.Name);
			instanceProperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			instanceProperty.Comments.Add(
				new CodeCommentStatement("<summary>\r\nThe only and single value of the class.\r\n</summary>", true));

			instanceProperty.GetStatements.Add(
				new CodeConditionStatement(
					new CodeBinaryOperatorExpression(
						new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), instance.Name),
						CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(null)),
					/* The statements to execute if the instance is null. */
					new CodeStatement[]
						{
							new CodeAssignStatement(
								new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), "instance"),
								new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(singletonClass.Name), getSection.Name))
						},
					/* The statements to execute if the instance is not null.*/
					new CodeStatement[]
						{
							new CodeConditionStatement(
								new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), refreshNeeded.Name),
								new CodeExpressionStatement(
									new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("System.Threading.Monitor"), "Enter",
									                               new CodeFieldReferenceExpression(
									                               	new CodeTypeReferenceExpression(singletonClass.Name), "locker"))),
								new CodeTryCatchFinallyStatement(
									new CodeStatement[]
										{
											new CodeAssignStatement(
												new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), refreshNeeded.Name),
												new CodePrimitiveExpression(false)),
											new CodeVariableDeclarationStatement(typeof (EventHandler), "temp",
											                                     new CodePropertyReferenceExpression(
											                                     	new CodeFieldReferenceExpression(
											                                     		new CodeTypeReferenceExpression(singletonClass.Name), "instance"),
											                                     	Strings.OnFileChangedDelegateName)),
											new CodeExpressionStatement(
												new CodeMethodInvokeExpression(new CodeSnippetExpression("ConfigurationManager"), "RefreshSection",
												                               new CodePrimitiveExpression(xmlFullNamespaceName))),
											new CodeAssignStatement(
												new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), instance.Name),
												new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(singletonClass.Name), getSection.Name)),
											new CodeAssignStatement(new CodePropertyReferenceExpression(
											                        	new CodeFieldReferenceExpression(
											                        		new CodeTypeReferenceExpression(singletonClass.Name), "instance"),
											                        	Strings.OnFileChangedDelegateName),
											                        new CodeVariableReferenceExpression("temp")),
											new CodeExpressionStatement(
												new CodeMethodInvokeExpression(
													new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), "instance"),
													Strings.OnFileChangedDelegateName + "Call",
													new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), "instance"),
													new CodeObjectCreateExpression("System.EventArgs")))
										}, new CodeCatchClause[] {},
									new CodeStatement[]
										{
											new CodeExpressionStatement(
												new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("System.Threading.Monitor"), "Exit",
												                               new CodeFieldReferenceExpression(
												                               	new CodeTypeReferenceExpression(singletonClass.Name), "locker")))
										}))
						}));

			instanceProperty.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), "instance")));

			singletonClass.Members.Add(instanceProperty);

			//commented out due to bug in CodeDom implementation
			/*
			CodeMemberEvent onReloadFile = new CodeMemberEvent();
			onReloadFile.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			onReloadFile.Type = new CodeTypeReference(typeof (EventHandler));
			onReloadFile.Name = "OnFileChanged";
			singletonClass.Members.Add(onReloadFile);
			 
			CodeDelegateCreateExpression onReloadFileDelegate =
				new CodeDelegateCreateExpression(new CodeTypeReference(typeof (EventHandler)), new CodeTypeReferenceExpression(singletonClass.Name), "OnFileChangedDelegate");

			CodeTypeConstructor staticConstructor = new CodeTypeConstructor();
			staticConstructor.Statements.Add(
				new CodeAttachEventStatement(
					new CodeEventReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), onReloadFile.Name),
					onReloadFileDelegate));

			singletonClass.Members.Add(staticConstructor);

			*/

			CodeMemberMethod onReloadFileDelegateMethod = new CodeMemberMethod();
			onReloadFileDelegateMethod.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			onReloadFileDelegateMethod.Name = "OnFileChangedFire";
			onReloadFileDelegateMethod.ReturnType = new CodeTypeReference(typeof(void));
			onReloadFileDelegateMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.Object", "sender"));
			onReloadFileDelegateMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.EventArgs", "e"));
			onReloadFileDelegateMethod.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(singletonClass.Name), refreshNeeded.Name), new CodePrimitiveExpression(true)));
			singletonClass.Members.Add(onReloadFileDelegateMethod);
		} 

		private void AddDummyNSProperty(CodeTypeDeclaration mainClass)
		{
			{
				CodeMemberProperty property = new CodeMemberProperty();
				property.Name = "DummyNS";
				property.Type = new CodeTypeReference("System.String");
				property.Attributes = MemberAttributes.Private;
				property.Comments.Add(
					new CodeCommentStatement(
						"<summary>\r\nDummy value corresponding to the application section namespace. Needs due to a bug in MS implementation.\r\n</summary>",
						true));
				property.GetStatements.Add(
					new CodeMethodReturnStatement(
						new CodeCastExpression("System.String",
											   new CodeIndexerExpression(new CodeBaseReferenceExpression(),
																		 new CodePrimitiveExpression("xmlns:" + XmlNamespaceName)))));
				CodeAttributeDeclaration cpAttribute =
					new CodeAttributeDeclaration("ConfigurationProperty",
												 new CodeAttributeArgument(
													new CodePrimitiveExpression("xmlns:" +
																				XmlNamespaceName)),
												 new CodeAttributeArgument("DefaultValue", new CodePrimitiveExpression(String.Empty)),
												 new CodeAttributeArgument("IsKey", new CodePrimitiveExpression(false)),
												 new CodeAttributeArgument("IsRequired", new CodePrimitiveExpression(false)));
				property.CustomAttributes.Add(cpAttribute);

				mainClass.Members.Add(property);
			}
		}

		private void ProcessSectionElementContent(CodeTypeDeclaration currentClass, XmlSchemaElement propertyElement)
		{
			if (propertyElement.RefName.IsEmpty)
			{
				AddOnReloadEventIfNotExists(currentClass, true);

				CodeMemberMethod onInit = new CodeMemberMethod();
				onInit.Attributes = MemberAttributes.Assembly;
				onInit.Name = "OnInitMembers";

				XmlSchemaComplexType complexType = propertyElement.ElementSchemaType as XmlSchemaComplexType;
				if (complexType != null && complexType.Name == null)
				{
					XmlSchemaParticle contentTypeParticle = complexType.ContentTypeParticle;
					if (contentTypeParticle is XmlSchemaGroupBase)
					{
						string childClassName = ConvertToClassName(propertyElement.Name);
						if (contentTypeParticle is XmlSchemaSequence)
						{
							XmlSchemaSequence particleElement = contentTypeParticle as XmlSchemaSequence;

							CodeTypeDeclaration childClass = new CodeTypeDeclaration();

							AddDefaultConstructor(childClass);

							ProcessConfigurationElement(childClass, propertyElement);

							ProcessAttributes(childClass, propertyElement);

							ProcessNestedElements(childClass, particleElement);

							if (CheckIfPropertyExists(currentClass, childClassName))
							{
								onInit.Statements.Add(
									new CodeAttachEventStatement(
										new CodeEventReferenceExpression(new CodeThisReferenceExpression(), Strings.OnFileChangedDelegateName),
										new CodeDelegateCreateExpression(new CodeTypeReference("System.EventHandler"),
										                                 new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),
										                                                                  childClassName),
										                                 Strings.OnFileChangedDelegateName + "Call")));
							}
							currentClass.Members.Add(childClass);
						}
						else if (contentTypeParticle is XmlSchemaChoice)
						{
							XmlSchemaChoice choiceParticle = contentTypeParticle as XmlSchemaChoice;
							if (choiceParticle.Items.Count > 0 && choiceParticle.Items[0] is XmlSchemaElement)
							{
								XmlSchemaElement particleElement = choiceParticle.Items[0] as XmlSchemaElement;

								CodeTypeDeclaration childClass = new CodeTypeDeclaration();

								AddDefaultConstructor(childClass);

								ProcessConfigurationElementCollection(childClass, propertyElement, particleElement);

								ProcessSectionElementContent(childClass, choiceParticle.Items[0] as XmlSchemaElement);
								ProcessAttributes(childClass, propertyElement);

								if (CheckIfPropertyExists(currentClass, childClassName))
								{
									onInit.Statements.Add(
										new CodeAttachEventStatement(
											new CodeEventReferenceExpression(new CodeThisReferenceExpression(), Strings.OnFileChangedDelegateName),
											new CodeDelegateCreateExpression(new CodeTypeReference("System.EventHandler"),
											                                 new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),
											                                                                  childClassName),
											                                 Strings.OnFileChangedDelegateName + "Call")));
								}
								currentClass.Members.Add(childClass);
							}
						}
					}
					else
					{
						CodeTypeDeclaration childClass = new CodeTypeDeclaration();

						AddDefaultConstructor(childClass);

						ProcessConfigurationElement(childClass, propertyElement);

						ProcessAttributes(childClass, propertyElement);

						currentClass.Members.Add(childClass);
					}
				}

				AddMethodOrStatetements(currentClass, onInit);
			}
		}

		private static bool CheckIfPropertyExists(CodeTypeDeclaration currentClass, string childClassName)
		{
			bool isPropertyExist = false;
			CodeMemberProperty property = null;
			foreach (CodeTypeMember member in currentClass.Members)
			{
				property = member as CodeMemberProperty;
				if (property != null && property.Name == childClassName)
				{
					isPropertyExist = true;
					break;
				}
			}
			return isPropertyExist;
		}

		private static void AddMethodOrStatetements(CodeTypeDeclaration currentClass, CodeMemberMethod onInit)
		{
			bool alreadyExists = false;
			CodeMemberMethod method = null;
			foreach (CodeTypeMember member in currentClass.Members)
			{
				method = member as CodeMemberMethod;
				if (method != null && method.Name == onInit.Name)
				{
					alreadyExists = true;
					//method.Statements.AddRange(onInit.Statements);
					onInit.Statements.AddRange(method.Statements);
					break;
				}
			}
			if (!alreadyExists)
			{
				currentClass.Members.Add(onInit);
			}
			else
			{
				currentClass.Members.Remove(method);
				currentClass.Members.Add(onInit);
			}
		}

		private static void AddDefaultConstructor(CodeTypeDeclaration childClass)
		{
			CodeConstructor constructor = new CodeConstructor();
			constructor.Attributes = MemberAttributes.Assembly;
			constructor.Statements.Add(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "OnInitMembers"));
			childClass.Members.Add(constructor);
		}

		private void ProcessNestedElements(CodeTypeDeclaration currentClass, XmlSchemaGroupBase choiceParticle)
		{
			foreach (XmlSchemaObject item in choiceParticle.Items)
			{
				if (item is XmlSchemaElement)
				{
					XmlSchemaElement propertyElement = item as XmlSchemaElement;
					CodeMemberProperty property = new CodeMemberProperty();
					property.Name = ConvertToClassName(propertyElement.Name);
					string propertyClassName = ConvertToClassName(propertyElement.Name) + "Element";
					property.Type = new CodeTypeReference(propertyClassName);
					property.Attributes = MemberAttributes.Public;
					CommentClassOrProperty(propertyElement, property);
					property.GetStatements.Add(
						new CodeMethodReturnStatement(
							new CodeCastExpression(propertyClassName,
												   new CodeIndexerExpression(new CodeBaseReferenceExpression(),
																			 new CodePrimitiveExpression(propertyElement.Name)))));

					CodeAttributeDeclaration cpAttribute =
						new CodeAttributeDeclaration("ConfigurationProperty",
													 new CodeAttributeArgument(
														new CodePrimitiveExpression(propertyElement.Name)));
					property.CustomAttributes.Add(cpAttribute);

					currentClass.Members.Add(property);

					//CodeTypeDeclaration childClass = new CodeTypeDeclaration();

					ProcessSectionElementContent(currentClass, propertyElement);

					//ProcessAttributes(childClass, propertyElement);

					//currentClass.Members.Add(childClass);
				}
			}
		}

		private void ProcessConfigurationElement(CodeTypeDeclaration childClass, XmlSchemaElement propertyElement)
		{
			childClass.Name = ConvertToClassName(propertyElement.Name) + "Element";
			childClass.IsClass = true;
			childClass.TypeAttributes = TypeAttributes.Public;
			childClass.IsPartial = IsCreatePartialClasses;
			CodeTypeReference baseRef = new CodeTypeReference("ConfigurationElement");
			childClass.BaseTypes.Add(baseRef);
			CommentClassOrProperty(propertyElement, childClass);
		}

		private void ProcessConfigurationElementCollection(CodeTypeDeclaration childClass, XmlSchemaElement propertyElement,
														   XmlSchemaElement particleElement)
		{
			string collectionItemName = particleElement.Name;
			childClass.Name = ConvertToClassName(propertyElement.Name) + "Element";
			childClass.IsClass = true;
			childClass.TypeAttributes = TypeAttributes.Public;
			childClass.IsPartial = IsCreatePartialClasses;
			CodeTypeReference baseRef = new CodeTypeReference("ConfigurationElementCollection");
			childClass.BaseTypes.Add(baseRef);
			CommentClassOrProperty(propertyElement, childClass);

			//find out if to use GetElementName
			bool shouldGenerateGetElementKeyMethod = true;
			if (propertyElement.UnhandledAttributes != null)
			{
				foreach (XmlAttribute unhandledAttribute in propertyElement.UnhandledAttributes)
				{
					if (unhandledAttribute.LocalName == "omit-key-element-processing")
					{
						string value = unhandledAttribute.Value;
						if (value == "true" || value == "yes" || value == "1")
						{
							shouldGenerateGetElementKeyMethod = false;
						}
					}
				}
			}

			if (shouldGenerateGetElementKeyMethod)
			{
				CodeMemberMethod getElementKeyMethod = new CodeMemberMethod();
				getElementKeyMethod.Name = "GetElementKey";
				getElementKeyMethod.ReturnType = new CodeTypeReference("System.Object");
				getElementKeyMethod.Attributes = MemberAttributes.Family | MemberAttributes.Override;
				getElementKeyMethod.Parameters.Add(
					new CodeParameterDeclarationExpression(new CodeTypeReference("ConfigurationElement"), "element"));
				getElementKeyMethod.Statements.Add(
					new CodeMethodReturnStatement(
						new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("element"), "GetHashCode")));
				childClass.Members.Add(getElementKeyMethod);
			}

			CodeMemberProperty collectionTypeProperty = new CodeMemberProperty();
			collectionTypeProperty.Name = "CollectionType";
			collectionTypeProperty.Type = new CodeTypeReference("ConfigurationElementCollectionType");
			collectionTypeProperty.Attributes = MemberAttributes.Public | MemberAttributes.Override;
			collectionTypeProperty.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ConfigurationElementCollectionType"),
														"BasicMap")));
			childClass.Members.Add(collectionTypeProperty);

			CodeMemberProperty elementNameProperty = new CodeMemberProperty();
			elementNameProperty.Name = "ElementName";
			elementNameProperty.Type = new CodeTypeReference("System.String");
			elementNameProperty.Attributes = MemberAttributes.Family | MemberAttributes.Override;
			elementNameProperty.GetStatements.Add(
				new CodeMethodReturnStatement(new CodePrimitiveExpression(collectionItemName)));
			childClass.Members.Add(elementNameProperty);

			CodeMemberMethod createNewElementMethod = new CodeMemberMethod();
			createNewElementMethod.Name = "CreateNewElement";
			createNewElementMethod.ReturnType = new CodeTypeReference("ConfigurationElement");
			createNewElementMethod.Attributes = MemberAttributes.Family | MemberAttributes.Override;
			createNewElementMethod.Statements.Add(
				new CodeMethodReturnStatement(new CodeObjectCreateExpression(ConvertToClassName(collectionItemName) + "Element")));
			childClass.Members.Add(createNewElementMethod);
		}

		private static void ProcessAttributes(CodeTypeDeclaration childClass, XmlSchemaElement propertyElement)
		{
			XmlSchemaObjectCollection attributesCollection =
				(((XmlSchemaComplexType)(propertyElement.ElementSchemaType))).Attributes;

			AddOnReloadEventIfNotExists(childClass, false);

			CodeMemberMethod onInit = new CodeMemberMethod();
			onInit.Attributes = MemberAttributes.Assembly;
			onInit.Name = "OnInitMembers";

			foreach (XmlSchemaAttribute attribute in attributesCollection)
			{
				string prefix = "";
				if (attribute.UnhandledAttributes != null)
				{
					foreach (XmlAttribute unhandledAttribute in attribute.UnhandledAttributes)
					{
						if (unhandledAttribute.LocalName == "prefix")
						{
							prefix = unhandledAttribute.Value;
						}
					}
				}

				CodeMemberProperty property = new CodeMemberProperty();
				CommentClassOrProperty(attribute, property);
				property.Name = "_lazy" + prefix + ConvertToClassName(attribute.Name);
				property.Type = new CodeTypeReference(attribute.AttributeSchemaType.Datatype.ValueType.FullName);
				property.Attributes = MemberAttributes.Public;
				property.GetStatements.Add(
					new CodeMethodReturnStatement(
						new CodeCastExpression(attribute.AttributeSchemaType.Datatype.ValueType.FullName,
											   new CodeIndexerExpression(new CodeBaseReferenceExpression(),
																		 new CodePrimitiveExpression(attribute.Name)))));

				List<CodeAttributeArgument> attributes = new List<CodeAttributeArgument>();
				attributes.Add(new CodeAttributeArgument(
								new CodePrimitiveExpression(attribute.Name)));

				if (attribute.DefaultValue != null && attribute.DefaultValue != String.Empty)
				{
					attributes.Add(new CodeAttributeArgument("DefaultValue", new CodePrimitiveExpression(attribute.DefaultValue)));
					attributes.Add(new CodeAttributeArgument("IsRequired", new CodePrimitiveExpression(false)));
				}

				CodeAttributeDeclaration cpAttribute =
					new CodeAttributeDeclaration("ConfigurationProperty", attributes.ToArray());
				property.CustomAttributes.Add(cpAttribute);

				childClass.Members.Add(property);

				CodeMemberField isToRefresh =
					new CodeMemberField(typeof(bool), "_isToRefresh" + prefix + ConvertToClassName(attribute.Name));
				isToRefresh.Attributes = MemberAttributes.Private;
				isToRefresh.InitExpression = new CodeSnippetExpression("true");
				childClass.Members.Add(isToRefresh);

				CodeMemberField lazyStorage =
					new CodeMemberField(attribute.AttributeSchemaType.Datatype.ValueType.FullName,
										"_lazyStorageOf" + prefix + ConvertToClassName(attribute.Name));
				lazyStorage.Attributes = MemberAttributes.Private;
				childClass.Members.Add(lazyStorage);

				CodeMemberMethod onReloadFileDelegateMethod = new CodeMemberMethod();
				onReloadFileDelegateMethod.Attributes = MemberAttributes.Private;
				onReloadFileDelegateMethod.Name = "_OnFileChangedFire" + prefix + ConvertToClassName(attribute.Name);
				onReloadFileDelegateMethod.ReturnType = new CodeTypeReference(typeof(void));
				onReloadFileDelegateMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.Object", "sender"));
				onReloadFileDelegateMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.EventArgs", "e"));
				onReloadFileDelegateMethod.Statements.Add(
					new CodeAssignStatement(
						new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isToRefresh.Name),
						new CodePrimitiveExpression(true)));
				childClass.Members.Add(onReloadFileDelegateMethod);

				onInit.Statements.Add(
					new CodeAttachEventStatement(
						new CodeEventReferenceExpression(new CodeThisReferenceExpression(), Strings.OnFileChangedDelegateName),
						new CodeDelegateCreateExpression(new CodeTypeReference("System.EventHandler"), new CodeThisReferenceExpression(),
														 onReloadFileDelegateMethod.Name)));

				property = new CodeMemberProperty();
				CommentClassOrProperty(attribute, property);
				property.Name = prefix + ConvertToClassName(attribute.Name);
				property.Type = new CodeTypeReference(attribute.AttributeSchemaType.Datatype.ValueType.FullName);
				property.Attributes = MemberAttributes.Public;
				property.GetStatements.AddRange(
					new CodeStatement[]
						{
							new CodeConditionStatement(
								new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isToRefresh.Name),
								// The statements to execute if the refresh is needed.
								new CodeAssignStatement(
									new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isToRefresh.Name),
									new CodePrimitiveExpression(false)),
								new CodeAssignStatement(
									new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), lazyStorage.Name),
									new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "_lazy" + property.Name))),
							new CodeMethodReturnStatement(
								new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), lazyStorage.Name))
						});


				childClass.Members.Add(property);
			}
			AddMethodOrStatetements(childClass, onInit);
		}

		private static void AddOnReloadEventIfNotExists(CodeTypeDeclaration childClass, bool makePublic)
		{
			bool isOnReloadFileDelegateExists = false;
			foreach (CodeTypeMember member in childClass.Members)
			{
				if (member is CodeMemberEvent && member.Name == Strings.OnFileChangedDelegateName)
				{
					isOnReloadFileDelegateExists = true;
					break;
				}
			}
			if (!isOnReloadFileDelegateExists)
			{
				CodeMemberEvent onReloadFile = new CodeMemberEvent();
				onReloadFile.Attributes = makePublic ? MemberAttributes.Public : MemberAttributes.Assembly;
				onReloadFile.Type = new CodeTypeReference(typeof(EventHandler));
				onReloadFile.Name = Strings.OnFileChangedDelegateName;
				childClass.Members.Add(onReloadFile);

				CodeMemberMethod onReloadFileCall = new CodeMemberMethod();
				onReloadFileCall.Attributes = MemberAttributes.Assembly;
				onReloadFileCall.Name = Strings.OnFileChangedDelegateName + "Call";
				onReloadFileCall.Parameters.Add(new CodeParameterDeclarationExpression("System.Object", "sender"));
				onReloadFileCall.Parameters.Add(new CodeParameterDeclarationExpression("System.EventArgs", "e"));
				onReloadFileCall.Statements.Add(
					new CodeConditionStatement(
						new CodeBinaryOperatorExpression(
							new CodePropertyReferenceExpression(new CodeThisReferenceExpression(),
																Strings.OnFileChangedDelegateName),
							CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("null")), new CodeExpressionStatement(
																											new CodeMethodInvokeExpression(
																												new CodeThisReferenceExpression(),
																												Strings.
																													OnFileChangedDelegateName,
																												new CodeVariableReferenceExpression
																													("sender"),
																												new CodeVariableReferenceExpression
																													("e")))));
				childClass.Members.Add(onReloadFileCall);
			}
		}

		private static void CommentClassOrProperty(XmlSchemaAnnotated annotated, CodeTypeMember property)
		{
			if (annotated.Annotation != null && annotated.Annotation.Items.Count > 0 &&
				annotated.Annotation.Items[0] is XmlSchemaDocumentation)
			{
				string commentText = ((XmlSchemaDocumentation)(annotated.Annotation.Items[0])).Markup[0].InnerText;
				commentText =
					String.Join("\r\n", commentText.Split(new char[] { '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries));
				commentText = Regex.Replace(commentText, @"^\s+\w", delegate(Match m)
																		{
																			string str = m.Captures[0].Value;
																			return str.Trim();
																		}, RegexOptions.Multiline);
				property.Comments.Add(new CodeCommentStatement(String.Format("<summary>\r\n{0}\r\n</summary>", commentText), true));
			}
		}

		private static string ConvertToClassName(string name)
		{
			string result = Regex.Replace(name, @"\W\w|^\w", delegate(Match m)
																{
																	string str = m.Captures[0].Value;
																	if (str.Length > 1)
																	{
																		str = str.Remove(0, 1);
																	}
																	return str.ToUpper();
																});
			return result;
		}

		#endregion properites

		#region helper functions

		#endregion helper functions
	}
}